package model;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.List;
import java.util.ResourceBundle;

import Bonus.AbstractBonus;
import Bonus.BonusPause;
import Bonus.BonusRewind;
import Bonus.BonusSmRocks;
import Bonus.BonusTorpedo;
import Bubble.Bubble;
import Bubble.BubblePath;
import Bubble.BubbleShot;
import Bubble.Bubbles;
import Collision.BonusCollision;
import Collision.BonusShotCollision;
import Collision.ShotCollision;
import Factory.BonusFactory;
import Factory.LevelFactory;
import Item.Item;
import Part.Part;
import Part.Parts;
import Part.PartBub;
import Part.PartString;
import PathPoint.Path;

import com.golden.gamedev.Game;
import com.golden.gamedev.object.CollisionManager;
import com.golden.gamedev.object.PlayField;
import com.golden.gamedev.object.Sprite;
import com.golden.gamedev.object.collision.BasicCollisionGroup;

public class ProgBob extends PlayField
{
    final static int RAD_BUBBLE = 13;
    final static int W_SMFISH = 8;
    final static int H_SMFISH = 6;
    final static int GS_MAINMENU = 0;
    final static int GS_PLAY = 1;
    final static int GS_LEVEL_COMPLETED = 2;
    final static int GS_GAME_OVER = 3;
    final static int ITEM_FREE = 0;
    final static int ITEM_BUBBLE = 1;
    final static int ITEM_TORPEDO = 2;
    final static int ITEM_BONUS = 3;
    final static int ITEM_SMROCKS = 4;
    final static int NUM_BOB_STATES = 11;
    final static int MAX_BUBBLES = 128;
    final static double MAX_TIME_PAUSED = 6;
    final static double MAX_TIME_REWIND = 4;
    final static double PI_2 = 2 * Math.PI;

    Font fnt;
    Font fnt2;
    Font fnt3;
    public Font fnts[];
    Sprite bm_bob[]; // bob is player

    Sprite bm_evil_fish;
    Sprite bm_smfish;
    Sprite bm_torpedo;
    Sprite bm_smrock;
    Sprite bm_fisho;
    Sprite bm_click_to_continue;
    Sprite bm_congrats;
    Sprite bm_bg_menu;
    Sprite bm_bg_game;
    Sprite bm_lev_comp;
    Sprite bm_loading;
    Sprite bm_game_over;
    String snd_level_start;
    String snd_explosion;
    String snd_shoot_bubble;
    String snd_plip_plop;
    String snd_pop;
    String snd_bob_loses;
    String snd_pick;
    String snd_swap;
    String snd_lev_comp;
    String snd_combo[];
    
    List<AbstractBonus> myBonusList;
    List<Item> myItems;
    public Path myPath;
    
    public boolean torpedo;
    public int smrocks;
    public double name_show;
    public int episode;
    public int sub_level;
    public int total_fish_saved;
    public int longest_combo;
    public double handicap;
    public int game_state;
    public boolean game_starting;
    public boolean completed_the_game;
    public double time_rewind;
    public double time_paused;
    
    //private Bonuses myBonuses;
    private Parts myParts;
    private BufferedImage PART_BUB_IMG;
    private BufferedImage SM_FISH_IMG;
    private Sprite bm_part_bub; // DEBUG
    
    public Item items; // Items are currently effective bonus
    public int num_path_points;
    
    public double path_t0;
    public double path_speed;
    public int fish_to_save;
    public int fish_to_save_at_start;
    public double path_last_t;
    public double path_start_t;
    
    ////////// Bubble variables
    /*
    Sprite bm_bubbles[];
    Bubble bubbles[];
    Bubble myBubblePath.getBubble(0);
    Bubble myBubbleShot.getBubble(0);
    Sprite next_bubble;
    Sprite next_bubble2;
    */
    private BubblePath myBubblePath;
    private BubbleShot myBubbleShot;
    private final LinkedList<Bubble> EMPTY_LIST = new LinkedList<Bubble>();
    private CollisionManager myShotCol;
    private CollisionManager myBonusCol;
    private CollisionManager myBonusShotCol;
    
    int bob_y;
    int bob_x;
    double bob_akey;
    double akey0;
    double akey1;
    double akey2;
    double akey3;
    double shoot_time;
    boolean game_over;
    double go_speedup;
    int level;
    boolean level_completed;
    double st_timer;
    int level_score;
    int total_score;
    int score_show;
    boolean paused;
    public int path_inc_x;
    public int path_inc_y;
    Game game;
    BonusFactory bonusFactory;
    LevelFactory levelFactory;

    //create a factory class to hande bonus states checking
    boolean isSomeBonusActing ()
    {
        return bonusFactory.state(this);
    }

    public void init (Game game)
    {
        this.game = game;
        fnt = Font.decode("Arial-BOLD-16");
        fnt2 = Font.decode("Arial-BOLD-20");
        fnt3 = Font.decode("Arial-PLAIN-14");
    
        fnts = new Font[11];
        for (int k = 0; k < 11; k++)
        {
            fnts[k] = Font.decode("Arial-PLAIN-" + (k + 2));
        }
        
        myParts = new Parts();
        addGroup(myParts);
        PART_BUB_IMG = game.getImage("resources/part_bub_01.png");
        SM_FISH_IMG = game.getImage("resources/smfish.png");
        
        for (int k=0; k<101; k++)
        {
        	myParts.add(new Part(10, 10, 10, 10, 0));
        }
        for (int k=0; k<100; k++)
        {
        	myParts.add(new PartBub(PART_BUB_IMG, 10, 10, 10, 10, 0));
        }
    
        bm_loading = new Sprite(game.getImage("resources/loading.jpg"));
        bm_click_to_continue = new Sprite(game.getImage("resources/click_to_continue.png"));
        bm_congrats = new Sprite(game.getImage("resources/congrats.png"));
        bm_bob = new Sprite[NUM_BOB_STATES];
        
        // add animated bubblefish       
        ResourceBundle fishImages = ResourceBundle.getBundle("resourceFile.fileImage");
        int index = 0;
        for (String s : fishImages.keySet())
        {
            bm_bob[index++] = new Sprite(game.getImage(fishImages.getString(s)));
        }
        bm_evil_fish = new Sprite(game.getImage("resources/evil_fish.png"));
        bm_smfish = new Sprite(game.getImage("resources/smfish.png"));
    
        // add combo sound
        ResourceBundle combo = ResourceBundle.getBundle("resourceFile.combo");
        index = 0;
        snd_combo = new String[combo.keySet().size()];
        for (String s : fishImages.keySet())
        {
            snd_combo[index++] = combo.getString(s);
        }
        snd_shoot_bubble = "resources/release_bubble.au";
        snd_plip_plop ="resources/pop_01.au";
        snd_pop = "resources/pop_01.au";
        snd_bob_loses = "resources/bob_loses.au";
        snd_pick = "resources/pickup.au";
        snd_swap = "resources/gulp.au";
        snd_lev_comp = "resources/lev_comp_song.au";
        snd_level_start = "resources/level_start.au";
        snd_explosion = "resources/explosion.au";
    
        //fish paths
        myBubblePath = new BubblePath(game, myPath, myParts);
        addGroup(myBubblePath);
        
        bm_bg_menu = new Sprite(game.getImage("resources/bg_menu.jpg"));
        bm_lev_comp = new Sprite(game.getImage("resources/lev_comp.png"));
        bm_game_over = new Sprite(game.getImage("resources/game_over.png"));
        bm_part_bub = new Sprite(game.getImage("resources/part_bub_04.png")); // DEBUG
        
        // Sprites of Bonuses
        bm_torpedo = new Sprite(game.getImage("resources/torpedo.png"));
        bm_smrock = new Sprite(game.getImage("resources/smrock.png"));
        bm_fisho = new Sprite(game.getImage("resources/fisho_full.png"));
    
        // Set up a LinkList that stores all types of Bonuses
        // Reflection can be used
        myBonusList = new LinkedList<AbstractBonus>();
        myBonusList.add(new BonusPause(this, "Pause", new Sprite(game.getImage("resources/bonus_pause.png"))));
        myBonusList.add(new BonusRewind(this, "Rewind", new Sprite(game.getImage("resources/bonus_rewind.png"))));
        myBonusList.add(new BonusTorpedo(this, "Torpedo", new Sprite(game.getImage("resources/bon_torpedo.png"))));
        myBonusList.add(new BonusSmRocks(this, "Small Rocks", new Sprite(game.getImage("resources/bon_smrocks.png"))));

        game_state = GS_MAINMENU;
        initLevel(1);
    }

    void initGame ()
    {
        total_fish_saved = 0;
        total_score = 0;
        level_score = 0;
        shoot_time = 0;
        completed_the_game = false;
        game_over = false;
        paused = false;
    }

    void initLevel (int level_num)
    {
        paused = false;
        level_completed = false;
        handicap = 1;
        longest_combo = 0;
        torpedo = false;
        smrocks = 0;
        name_show = 0;
        time_rewind = 0;
        time_paused = 0;
        items = null;
        
        myItems = new LinkedList<Item>();
        
        game_starting = true;
        level_score = 0;
        go_speedup = 0;
        st_timer = 0;
        // myBubbleShot.getBubble(0) is set up in myBubbleShot
        /*
        myBubbleShot.getBubble(0) = new Bubble();
        myBubbleShot.getBubble(0).bm = null;
        */
        // TODO: add new myBubbleShot.getBubble(0)?
        path_speed = 0.5;
        bob_y = 290;
        bob_akey = 0;
        level = level_num;
        
        // level factory
        episode = (level - 1) / 5;
        sub_level = (level - 1) % 5 + 1;
        levelFactory.initLevel(this);
        path_speed += episode * 0.08;
        path_t0 = 0;
        path_last_t = 0;
        for (int k = 0; k < num_path_points; k++)
        {
        	path_last_t += myPath.getDistance(k);
        }
        path_last_t /= 2 * RAD_BUBBLE;
        myBubbleShot = new BubbleShot(game, episode, myPath, myParts);
        addGroup(myBubbleShot);
        
        // add collision
        // TODO: myBonuses must extends SpriteGroup
        myShotCol = new ShotCollision();
        addCollisionGroup(myBubblePath, myBubbleShot, myShotCol);
        myBonusCol = new BonusCollision();
        addCollisionGroup(myBonuses, myBubbleShot, myBonusCol);
        myBonusShotCol = new BonusShotCollision();
        addCollisionGroup(myBonuses, myBubblePath, myBonusShotCol);
    }

    public void draw (Graphics2D context)
    {
        if (game_state == GS_MAINMENU)
        {
            bm_bg_menu.render(context);
        }
        else if (game_state == GS_LEVEL_COMPLETED)
        {
            bm_bg_game.render(context, 0, 0);
            bm_lev_comp.render(context, 90, 30);
            context.setColor(Color.WHITE);
            context.setFont(fnt);
            drawOutlined(context, 340, 151, "Fish Saved:");
            drawOutlined(context, 340, 168, "" + total_fish_saved);
            drawOutlined(context, 140, 151, "Max Combo:");
            drawOutlined(context, 140, 168, "" + longest_combo);
            drawOutlined(context, 240, 205, "Level Score:");
            drawOutlined(context, 240, 222, "" + level_score);
            context.setFont(fnt);
            context.setColor(Color.WHITE);
            bm_click_to_continue.render(context, 156, 290);
            drawParts(context);
            drawBar(context);
        }
        else if (game_state == GS_GAME_OVER)
        {
            bm_bg_game.render(context, 0, 0);
            if (completed_the_game)
            {
                context.setColor(Color.WHITE);
                context.setFont(fnt2);
                bm_congrats.render(context, 50, 30);
                context.setColor(Color.WHITE);
                context.setFont(fnt);
                drawOutlined(context, 340, 175, "Fish Saved:");
                drawOutlined(context, 340, 192, "" + total_fish_saved);
                drawOutlined(context, 140, 175, "Final Score:");
                drawOutlined(context, 140, 192, "" + total_score);
            }
            else
            {
                bm_game_over.render(context, 132, 30);
                context.setColor(Color.WHITE);
                context.setFont(fnt);
                drawOutlined(context, 240, 111, "Fish Saved:");
                drawOutlined(context, 240, 128, "" + total_fish_saved);
                drawOutlined(context, 240, 155, "Final Score:");
                drawOutlined(context, 240, 172, "" + total_score);
            }
            drawParts(context);
            drawBar(context);
            bm_click_to_continue.render(context, 156, 290);
        }
        else if (game_state == GS_PLAY)
        {
            bm_bg_game.render(context, 0, 0);
            drawPath(context);
            drawParts(context);
            drawItems(context);
            bob_x = game.getMouseX();
            int y_offset = (int)(5 * Math.sin(shoot_time * Math.PI));
            if (torpedo) // boolean
            {
                // sprite
            	bm_torpedo.setX(bob_x - bm_torpedo.getWidth() / 2);
                bm_torpedo.setY(bob_y - bm_torpedo.getHeight() / 2 + y_offset);
                bm_torpedo.render(context);
            }
            else if (smrocks > 0) // double
            {
                bm_smrock.setX(bob_x - bm_torpedo.getWidth() / 2);
                bm_smrock.setY(bob_y - bm_torpedo.getHeight() / 2 + y_offset);
                bm_smrock.render(context);
            }
            else
            {
                if (shoot_time >= 1)
                {
                    /*
                	next_bubble.setX(bob_x - next_bubble.getWidth() / 2);
                    next_bubble.setY(bob_y - next_bubble.getHeight() + y_offset);
                    next_bubble.render(context);
                    */
                	myBubbleShot.setLocation(1, 
                			bob_x - myBubbleShot.getBubble(1).getWidth()/2, 
                			bob_y - myBubbleShot.getBubble(1).getHeight() + y_offset);
                	myBubbleShot.getBubble(1).render(context);
                }
                else
                {
                    int x_offset = (int)(shoot_time * 2 * RAD_BUBBLE);
                    /*
                    next_bubble.setX(bob_x - x_offset / 2);
                    next_bubble.setY((bob_y + (1 - shoot_time) * 4 * RAD_BUBBLE) - next_bubble.getHeight() + y_offset);
                    next_bubble.render(context);
                    */
                    myBubbleShot.setLocation(1, 
                			bob_x - x_offset/2, 
                			bob_y - + (1-shoot_time)*4*RAD_BUBBLE - myBubbleShot.getBubble(1).getHeight() + y_offset);
                	myBubbleShot.getBubble(1).render(context);
                }
                // BUGBUG: change size to 16x16
                /*
                next_bubble2.render(context,  
                                    (int)((bob_x - 16 / 2) + 1), 
                                    (int)(((bob_y - 4) + y_offset) + (1 - shoot_time) * 16));
                                    */
                myBubbleShot.getBubble(2).render(context,
                		(int)((bob_x - 16 / 2) + 1), 
                        (int)(((bob_y - 4) + y_offset) + (1 - shoot_time) * 16));
            }
            bm_bob[(int)bob_akey].render(context, bob_x - 22, bob_y + y_offset);
            drawBar(context);
            if (name_show < 1 && !paused)
            {
                context.setFont(fnt2);
                context.setColor(Color.BLACK);
                drawOutlined(context, 240, 170, "Level " + (episode + 1) + "-" + sub_level);
            }
        }
        if (paused)
        {
            context.setFont(fnt2);
            context.setColor(Color.BLACK);
            drawOutlined(context, 240, 170, "Paused");
            context.setFont(fnt);
            context.setColor(Color.BLACK);
            context.drawString("Press space to continue", 240, 190);
        }
    }

    void drawParts (Graphics2D context)
    {
    	myParts.render(context);
    }

    void drawPath (Graphics2D context)
    {
        int x = 0;
        int y = 0;
        double t = 0;
        for (Bubble bubble = myBubblePath.getBubble(0); bubble != null; bubble = bubble.next)
        {
            if (path_t0 + bubble.t < path_last_t - 1)
            {
                if (bubble.fish_inside)
                {
                    bm_smfish.setX(bubble.x - 8);
                    bm_smfish.setY(bubble.y - 6 + 2 * Math.sin(bubble.phase * PI_2));
                    bm_smfish.render(context);
                }
                // Get coordinates for bubble and show bubble
                double x_scale = 1 - bubble.trans;
                x = (int)(bubble.trans * bubble.attach_x + x_scale * bubble.x);
                if (path_t0 + bubble.t >= path_last_t - 4)
                {
                    double y_scale = ((path_t0 + bubble.t) - (path_last_t - 4)) / 4;
                    y = (int)((bubble.trans * bubble.attach_y + x_scale * bubble.y) + 3 * Math.sin(bubble.phase * PI_2) * (1 - y_scale) + 4 * Math.sin(PI_2 * akey2) * y_scale);
                }
                else
                {
                    y = (int)(bubble.trans * bubble.attach_y + x_scale * bubble.y + 3 * Math.sin(bubble.phase * PI_2));
                }
                bubble.bm.render(context, x - RAD_BUBBLE, y - RAD_BUBBLE);
            }
            t = bubble.t;
        }
    
        t += path_t0 + 1 + st_timer * (path_t0 + path_last_t - t);
        for (double t1 = Math.floor(t); t1 < path_last_t; t1 += 0.5)
        {
        	Point2D.Double pnt = myPath.getPathPoint(t1);
            y = (int)(pnt.y + 3 * Math.sin(PI_2 * akey1 + t1));
            /*
            bm_part_bub[3].render(context, (int)pnt.x - 2, y - 2);
            */
            bm_part_bub.render(context, (int)pnt.x - 2, y - 2);
        }

        x = (int) (myPath.getPoint(num_path_points-1).x - 55 + (1 - Math.sin(0.5 * Math.PI + 0.5 * Math.PI * st_timer)) * (480 - x));
        y = (int) myPath.getPoint(num_path_points-1).y;
        bm_evil_fish.render(context, x, (y + (int)(4 * Math.sin(PI_2 * akey2))) - 40);
    }

    void drawItems (Graphics2D context)
    {
        for (Item item = items; item != null; item = item.next)
        {
            int x = (int)(item.x + 3 * Math.sin(PI_2 * akey1));
            int y = (int) item.y;
            if (4 * item.time_existed < 1 && item.vel_y > 0)
            {
                double t = 4 * item.time_existed;
                int w = (int)(item.bm.getWidth() * t);
                int h = (int)(item.bm.getHeight() * t);
                // BUGBUG: change size
                item.bm.render(context, x - w / 2, y - h / 2);
            }
            else
            {
                item.bm.render(context, x, y);
            }
        }
        
        if (myBubbleShot.getBubble(0).bm != null)
        {
            myBubbleShot.getBubble(0).bm.render(context, (int)myBubbleShot.getBubble(0).x, (int)myBubbleShot.getBubble(0).y);
        }
    }

    void drawBar (Graphics2D context)
    {
        int w = bm_fisho.getWidth();
        int h = (int)(w * ((fish_to_save_at_start - fish_to_save) / fish_to_save_at_start));
        // BUGBUG: change size
        bm_fisho.render(context, 276, 342);
        context.setColor(Color.WHITE);
        context.setFont(fnt);
        context.drawString(scoreString(), 63, 354);
        context.drawString((episode + 1) + " - " + sub_level, 190, 354);
    }

    void drawOutlined (Graphics2D context, int x, int y, String text)
    {
        // BUGBUG: center
        context.setColor(Color.BLACK);
        context.drawString(text, x - 2, y - 2);
        context.drawString(text, x - 2, y + 2);
        context.drawString(text, x + 2, y + 2);
        context.drawString(text, x + 2, y - 2);
        context.drawString(text, x - 2, y);
        context.drawString(text, x, y + 2);
        context.drawString(text, x + 2, y);
        context.drawString(text, x, y - 2);
        context.setColor(new Color(0xECD300));
        context.drawString(text, x, y);
    }

    public void update (double elapsedTime)
    {
        if (paused || game_state != GS_PLAY)
        {
            return;
        }
        if (!level_completed && !game_over && !existsInPath(next_bubble))
        {
            next_bubble = getRandomBubble(false);            
        }
        akey0 = (akey0 + elapsedTime) % 1.0;
        akey1 = (akey1 + 0.7 * elapsedTime) % 1.0;
        akey2 = (akey2 + 0.5 * elapsedTime) % 1.0;
        akey3 = (akey3 + 0.3 * elapsedTime) % 1.0;
        bob_akey = (bob_akey + 2 * elapsedTime) % NUM_BOB_STATES;
        time_paused = Math.max(0, time_paused - elapsedTime);
        time_rewind = Math.max(0, time_rewind - elapsedTime);
        shoot_time = Math.min(1, shoot_time + 3 * elapsedTime);
        name_show += 0.7 * elapsedTime;
        score_show += Math.min(4, (int)(5 * elapsedTime * (total_score - score_show)));
        score_show = Math.min(score_show, total_score);
        updateParts(elapsedTime);
        updateItems(elapsedTime);
        updateBubbles(elapsedTime);
    }

    void updateParts (double time)
    {
    	myParts.update((long) time*1000);
    }

    void updateItems (double time)
    {
        Item dead = null;
        
        for (Item item = items; item != null; item = item.next)
        {
            item.y += time * item.vel_y;
            if (item.y < -21 || item.y > 380 || item.type == ITEM_FREE)
            {
                if (dead == null)
                {
                    items = items.next;
                }
                else
                {
                    dead.next = item.next;
                }
            }
            else
            {
                item.time_existed += time;
                if (item.type == ITEM_TORPEDO || item.type == ITEM_SMROCKS)
                {
                    if (item.type == ITEM_TORPEDO)
                    {
                        item.vel_y -= 400 * time;
                    }
                    for ( ; Math.abs(item.y - item.py) > 4; item.py -= 4)
                    {
                    	myParts.add(new PartBub(PART_BUB_IMG,
                    			item.x+myParts.randDouble(-5, 5), item.py, 
                    			myParts.randDouble(-10, 10),myParts.randDouble(-50, 50),
                    			myParts.randDouble(1, 2.2)));
                    }
                    for (Bubble bubble = myBubblePath.getBubble(0); bubble != null; bubble = bubble.next)
                    {
                        double dx = item.x - bubble.x;
                        double dy = item.y - bubble.y;
                        if (dx * dx + dy * dy < RAD_BUBBLE * RAD_BUBBLE)
                        {
                            if (item.type == ITEM_TORPEDO)
                            {
                                for (Bubble bubble1 = myBubblePath.getBubble(0); bubble1 != null; bubble1 = bubble1.next)
                                {
                                	myParts.spawnBiggerBurst(item.x, item.y);
                                    double ddx = item.x - bubble1.x;
                                    double ddy = item.y - bubble1.y;
                                    if (ddx * ddx + ddy * ddy < 4096 && path_t0 + bubble1.t > 0)
                                    {
                                    	myParts.spawnBurst(bubble1.x, bubble1.y);
                                        if (bubble1.fish_inside)
                                        {
                                            fish_to_save--;
                                            total_fish_saved++;
                                            myParts.add(new PartBub(SM_FISH_IMG,
                                            		bubble1.x, bubble1.y, 
                                            		myParts.randDouble(-180, -140), myParts.randDouble(-20, 20),
                                            		0.4));
                                        }
                                        // shot bubble1?
                                        if (bubble1.next != null)
                                        {
                                        	bubble1.next.shot = true;
                                            bubble1.next.prev = bubble1.prev;
                                        }
                                        if (bubble1.prev != null)
                                        {
                                            bubble1.prev.next = bubble1.next;
                                        }
                                        else
                                        {
                                            myBubblePath.getBubble(0) = bubble1.next;
                                        }
                                    }
                                }
                                game.playSound(snd_explosion);
                                shoot_time = 0.5;
                            }
                            else if (item.type == ITEM_SMROCKS)
                            {
                            	myParts.spawnBurst(bubble.x, bubble.y);
                                if (bubble.fish_inside)
                                {
                                    fish_to_save--;
                                    total_fish_saved++;
                                    myParts.add(new PartBub(SM_FISH_IMG, 
                                    		bubble.x, bubble.y,
                                    		myParts.randDouble(-180, -140), myParts.randDouble(-20, 20),
                                    		0.4));
                                }
                                if (bubble.next != null)
                                {
                                    bubble.next.shot = true;
                                    bubble.next.prev = bubble.prev;
                                }
                                if (bubble.prev != null)
                                {
                                    bubble.prev.next = bubble.next;
                                }
                                else
                                {
                                    myBubblePath.getBubble(0) = bubble.next;
                                }
                                game.playSound(snd_pop);
                            }
                            
                            // Bonus is released after used
                            item.type = ITEM_FREE;
                            if (myBubblePath.getBubble(0) == null)
                            {
                            	if (fish_to_save <= 0)
                                {
                            		// Complete a level
                            		level_completed = true;
                                }
                                else
                                {
                                    // Continuously generate new bubble in path
                                	myBubblePath.getBubble(0) = new Bubble();
                                    myBubblePath.getBubble(0).t = -path_t0 - 1;
                                    myBubblePath.getBubble(0).bm = getRandomBubble(true);
                                    myBubblePath.getBubble(0).fish_inside = true;
                                }
                            }
                        }
                    }
                }
                else // if item is pausing or rewinding
                {
                    for ( ; Math.abs(item.y - item.py) > 4; item.py += 4)
                    {
                    	myParts.add(new PartBub(PART_BUB_IMG, 
                    			item.x+myParts.randDouble(-7, 7), item.py,
                    			myParts.randDouble(-5, 5), myParts.randDouble(-50, -10),
                    			myParts.randDouble(1, 2.2)));
                    }
                    if (item.bonus != null && Math.abs(item.x - bob_x) < 25 && Math.abs(item.y - (bob_y + 20)) < 38)
                    {
                    	// Pickup a bonus?
                    	myParts.spawnBurst(item.x, item.y);
                        game.playSound(snd_pick);
                        myParts.add(new PartString(fnts[9],
                        		item.x, item.y,
                        		0, -30,
                        		item.bonus.getName(),0.6));
                        shoot_time = 0.5;
                        
                        // TODO: Bonus acts!!
                        // this bonus (pause or rewind) effects immediately after picking up
                        item.bonus.act();
                        // Set bonus as used
                        if (dead == null)
                        {
                            items = items.next;
                        }
                        else
                        {
                            dead.next = item.next;
                        }
                    }
                }
                dead = item;
            }
        }
    }

    void updateBubbles (double elapsedTime)
    {
    	double acc = path_speed * 1.5;
        Bubble bubble = myBubblePath.getLastBubble();
        if (bubble!=null)
        {
        	// mathematics.. #$^$#^@#$@#!
        	
        	if (game_starting)
            {
                double t = path_t0 + bubble.getT();
                if (t < path_start_t - 8)
                {
                    acc *= 25;
                }
                else if (t < path_start_t)
                {
                    acc *= 1 + (24 * (path_start_t - t)) / 8;
                }
                else
                {
                    game_starting = false;
                }
            } // if (game_starting)
            double t = (path_t0 + bubble.getT()) / path_last_t;
            if (time_paused <= 0)
            {
                if (t < 0.7)
                {
                    if (t > 0.1)
                    {
                        handicap += 0.1 * (0.7 - t) * elapsedTime;
                    }
                    else
                    {
                        handicap += 0.06 * elapsedTime;
                    }
                }
                else if (t > 0.7)
                {
                    handicap -= 0.15 * (t - 0.7) * elapsedTime;
                }
            } // if (time_paused <= 0)
            handicap = Math.max(0.95, Math.min(handicap, 4));
            acc *= handicap;
            if (t < 0.4)
            {
                t = 1 + 15 * (0.4 - t);
            }
            else if (t > 0.8)
            {
                if (t > 0.95)
                {
                    t = 0.95;
                }
                t = 2.5 * (1 - t);
                if (t < 0.15)
                {
                    t = 0.15;
                }
            }
            else
            {
                t = 0.5 + 0.5 * (1 - (t - 0.4) / 0.4);
            }
            acc *= t;
        } // if (bubble!=null)
        
        acc = Math.max(0.2, acc);
        
        if (time_paused > 0)
        {
            double factor = 1;
            if (time_paused > 5)
            {
                factor = 1 - (MAX_TIME_PAUSED - time_paused);
            }
            else if (time_paused > 1)
            {
                factor = 0;
            }
            else if (time_paused > 0)
            {
                factor = 1 - time_paused;
            }
            acc *= factor;
        } // if (time_paused > 0)
        
        if (time_rewind > 0)
        {
            double factor = 0;
            if (time_rewind > 3)
            {
                factor = 4 - time_rewind;
            }
            else if (time_rewind > 1)
            {
                factor = 1;
            }
            else if (time_rewind > 0)
            {
                factor = 1 * time_rewind;
            }
            acc = acc * (1 - factor) - 3 * factor;
        } // if (time_rewind > 0)
        
        if (game_over)
        {
            acc += go_speedup;
            go_speedup += 32 * elapsedTime;
        }
        else
        {
            acc = Math.max(-12, Math.min(acc, 12));
        } // if (game_over)
        acc *= elapsedTime;
        
        if (bubble != null && (path_t0 + bubble.getT() > 0 || acc > 0))
        {
            path_t0 += acc;
        } 
        
        if (game_over)
        {
            if (myBubblePath.getList() == null)
            {
                st_timer += elapsedTime;
            }
            if (st_timer > 1)
            {
                game_state = GS_GAME_OVER;
            }
        } // if (game_over)
        
        if (level_completed)
        {
            st_timer += elapsedTime;
            if (st_timer > 1)
            {
                if (game_state != GS_LEVEL_COMPLETED)
                {
                    game.playSound(snd_lev_comp);
                }
                game_state = GS_LEVEL_COMPLETED;
            }
            return;
        } // if (level_completed)
        
        double shot_y = myBubbleShot.getList().get(0).getY();
        myBubbleShot.getList().get(0).setY(shot_y - 620 * elapsedTime);
        
        if (myBubbleShot.getList() != null)
        {
            for (double part_y = myBubbleShot.getList().get(0).getY(); part_y < shot_y; part_y += 5)
            {
                myBubbleShot.getBubble(0).setLocation(
            			myBubbleShot.getBubble(0).getX(), 
            			myBubbleShot.getBubble(0).getY()-620*elapsedTime);
            	double x = myBubbleShot.getBubble(0).getX();
            	double y = myBubbleShot.getBubble(0).getY();
            	myParts.add(new PartBub(PART_BUB_IMG, x+myParts.randDouble(-7, 7), y, 
            			myParts.randDouble(-10, 10), myParts.randDouble(-40, 0), myParts.randDouble(3, 4.2)));
            }
        }
        
        while (!game_over && fish_to_save > 0 && myBubblePath.getList().getFirst().getX() > -2 * RAD_BUBBLE) 
        {
            Bubble firstBubble = myBubblePath.getList().get(0);
        	Bubble bubbleTmp = myBubblePath.getRandomBubble(true, EMPTY_LIST);
            bubbleTmp.setPhase(firstBubble.getPhase()-0.1);
            bubbleTmp.setT(firstBubble.getT()-1);
            Bubbles.updateBubbleLocation(path_t0, bubbleTmp);
            myBubblePath.addFirst(bubbleTmp);
        }
        
        
        // TODO: setFish, setScore, setTime
        // following codes in this method is done in collided method
    }

    public void handleInput ()
    {
        // Should items handled in handleInput method?
    	if (game.keyPressed(KeyEvent.VK_SPACE))
        {
            if (game_state == GS_PLAY)
            {
                game.playSound(snd_pick);
                paused = !paused;
            }
            else
            {
                paused = false;
            }
        }
        if (game.click())
        {
            if (game_state == GS_LEVEL_COMPLETED)
            {
                game.playSound(snd_level_start);
                game_state = GS_PLAY;
                game_over = false;
                if (level / 5 + 1 >= 4)
                {
                    completed_the_game = true;
                    game_state = GS_GAME_OVER;
                }
                initLevel(level + 1);
            }
            else if (game_state == GS_GAME_OVER)
            {
                game.playSound(snd_level_start);
                game_state = GS_PLAY;
                game_over = false;
                initGame();
                initLevel(1);
            }
            else if (game_state == GS_MAINMENU)
            {
                game.playSound(snd_level_start);
                game_state = GS_PLAY;
                game_over = false;
                initGame();
                initLevel(1);
            }
            else if (game_state == GS_PLAY && !paused)
            {
                if (smrocks > 0)
                {
                    game.playSound(snd_shoot_bubble);
                    smrocks--;
                    
                    // Create a new item as small rock and attach it to the list of items
                    Item item = new Item();
                    item.x = game.getMouseX();
                    item.y = item.py = bob_y;
                    item.vel_y = -360;
                    item.type = ITEM_SMROCKS;
                    item.bonus = null;
                    item.bm = bm_smrock;
                    if (items == null)
                    {
                        items = item;
                    }
                    else
                    {
                        item.next = items.next;
                        items.next = item;
                    }
                    shoot_time = 0.5;
                    myParts.add(new PartString(fnts[9],
                    		item.x, item.y, 0, -30, "" + smrocks, 0.6));
                }
                else if (torpedo)
                {
                    torpedo = false;
                    game.playSound(snd_shoot_bubble);
                    Item item = new Item();
                    item.x = game.getMouseX();
                    item.y = item.py = bob_y;
                    item.vel_y = -120;
                    item.type = ITEM_TORPEDO;
                    item.bonus = null;
                    item.bm = bm_torpedo;
                    if (items == null)
                    {
                        items = item;
                    }
                    else
                    {
                        item.next = items.next;
                        items.next = item;
                    }
                    shoot_time = 0.5;
                }
                else if (myBubbleShot.getBubble(0).bm != null)
                {
                    return;
                }
                else if (shoot_time < 0.8)
                {
                    return;
                }
                else
                {
                    // Shoot by bubbles and generate new bubbles
                	game.playSound(snd_shoot_bubble);
                    myBubbleShot.getBubble(0).bm = next_bubble;
                    next_bubble = next_bubble2;
                    next_bubble2 = getRandomBubble(false);
                    myBubbleShot.getBubble(0).x = game.getMouseX();
                    myBubbleShot.getBubble(0).y = bob_y - 10;
                    shoot_time = 0;
                }
            }
        }
        if (game.rightClick())
        {
            // Swap bubbles
        	game.playSound(snd_swap);
            Sprite next = next_bubble;
            next_bubble = next_bubble2;
            next_bubble2 = next;
            shoot_time = 0.5;
        }
    }

    /*
    boolean existsInPath (Sprite bub)
    {
        for (Bubble bubble = myBubblePath.getBubble(0); bubble != null; bubble = bubble.next)
        {
            if (bubble.bm == bub)
            {
                return true;
            }
        }
        return false;
    }
    */

    boolean adjIsGoingToBurst (Bubble bubble)
    {
        Sprite s = bubble.bm;
        int i = 1;
        for (Bubble b = bubble.prev; b != null && b.bm == s; b = b.prev)
            i++;
        for (Bubble b = bubble.next; b != null && b.bm == s; b = b.next)
            i++;
        return i >= 3;
    }
    
    AbstractBonus getRandomBonus ()
    {
        AbstractBonus bonus = myBonusList.get(randInt(myBonusList.size()-1));
        return bonus;
    }

    int randInt (int max)
    {
        return (int)(Math.random() * max);
    }

    double randDouble (double min, double max)
    {
        return min + Math.random() * (max - min);
    }

    String scoreString ()
    {
        StringBuffer stringbuffer = new StringBuffer(score_show);
        for (int i = stringbuffer.length() - 1 - 2; i > 0; i -= 3)
        {
            stringbuffer.insert(i, "0");            
        }
        return stringbuffer.toString();
    }
}